--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
meshchatx/src/backend/rns_link_manager.py c0b0ddffd735a0d007c1be9a5e8563ec42fdafc0 (c0b0ddff) Text, 17.12 KB
T8b949e# SPDX-License-Identifier: 0BSD
Tff7b72import T7ee787asyncio
Tff7b72import T7ee787base64
Tff7b72import T7ee787threading
Tff7b72import T7ee787time
Tff7b72from T7ee787collectionsT7ee787.T7ee787abc Tff7b72import Te6edf3Callable
Tff7b72from T7ee787typing Tff7b72import Te6edf3Optional
Tff7b72import T7ee787RNS
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backend Tff7b72import Te6edf3reticulum_pathfinding
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787reticulum_pathfinding Tff7b72import Te6edf3ReticulumLike
T8b949e# Cache of established RNS Links keyed by (aspect_str, destination_hash_bytes).
T8b949e# Kept separate from nomadnet_downloader.nomadnet_cached_links, as the two caches
T8b949e# may merge in the future if NomadNet is ported onto this generic Links API.
Te6edf3rns_cached_linksTb4b4b4: Tffa657dictTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657bytesTb4b4b4]Tb4b4b4, Ta5d6ff"Ta5d6ffRNS.LinkTa5d6ff"Tb4b4b4] Tff7b72= Tb4b4b4{Tb4b4b4}
Te6edf3_rns_link_last_usedTb4b4b4: Tffa657dictTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657bytesTb4b4b4]Tb4b4b4, Tffa657floatTb4b4b4] Tff7b72= Tb4b4b4{Tb4b4b4}
Te6edf3_rns_links_lock Tff7b72= Te6edf3threadingTff7b72.Td2a8ffLockTb4b4b4(Tb4b4b4)
T8b949e# Per-cache-key count of consecutive RNS.Link request failures. Reset on
T8b949e# successful response, and cleared whenever the cached link at the key is
T8b949e# replaced or evicted. Guarded by _rns_links_lock (same lock as the cache,
T8b949e# as the two are mutated together).
Te6edf3_link_failure_countsTb4b4b4: Tffa657dictTb4b4b4[Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657bytesTb4b4b4]Tb4b4b4, Tffa657intTb4b4b4] Tff7b72= Tb4b4b4{Tb4b4b4}
T8b949e# Number of consecutive request failures on a cached link that triggers a
T8b949e# teardown + cache eviction. The next request to the same destination will
T8b949e# then go through the full open_link path and re-establish.
Te6edf3_LINK_RECYCLE_FAILURE_THRESHOLD Tff7b72= T79c0ff2
T8b949e# Cap active RNS links retained in process memory.
Te6edf3MAX_CACHED_LINKS Tff7b72= T79c0ff64
Te6edf3LINK_IDLE_TTL_S Tff7b72= T79c0ff30 Tff7b72* T79c0ff60
T8b949e# Wait granularity while polling for path / link (seconds).
Te6edf3_POLL_INTERVAL_S Tff7b72= T79c0ff0.02
Tff7b72def Td2a8ffcached_link_countTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657intTb4b4b4:
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Tff7b72return Tffa657lenTb4b4b4(Te6edf3rns_cached_linksTb4b4b4)
Tff7b72def Td2a8ffget_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4)Tb4b4b4:
T8b949e"""Return a cached link if present and ACTIVE; drop stale entries."""
Te6edf3key Tff7b72= Tb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Te6edf3link Tff7b72= Te6edf3rns_cached_linksTff7b72.Td2a8ffgetTb4b4b4(Te6edf3keyTb4b4b4)
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72if Te6edf3linkTff7b72.Td2a8ffstatus Tff7b72is Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffACTIVETb4b4b4:
Te6edf3_rns_link_last_usedTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3link
Tff7b72tryTb4b4b4:
Tff7b72del Te6edf3rns_cached_linksTb4b4b4[Te6edf3keyTb4b4b4]
Tff7b72except Tf85149KeyErrorTb4b4b4:
Tff7b72pass
Te6edf3_rns_link_last_usedTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ff_teardown_linksTb4b4b4(Te6edf3linksTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72for Te6edf3link Tff7b72in Te6edf3linksTb4b4b4:
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72continue
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72def Td2a8ff_evict_over_cap_lockedTb4b4b4(Te6edf3preserve_keyTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Evict oldest links until at or under MAX_CACHED_LINKS. Caller holds lock."""
Te6edf3to_teardown Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72while Tffa657lenTb4b4b4(Te6edf3rns_cached_linksTb4b4b4) Tff7b72> Te6edf3MAX_CACHED_LINKSTb4b4b4:
Te6edf3candidates Tff7b72= Tb4b4b4[
Te6edf3k Tff7b72for Te6edf3k Tff7b72in Te6edf3rns_cached_links Tff7b72if Te6edf3preserve_key Tff7b72is Tff7b72None Tff7b72or Te6edf3k Tff7b72!= Te6edf3preserve_key
Tb4b4b4]
Tff7b72if Tff7b72not Te6edf3candidatesTb4b4b4:
Tff7b72break
Te6edf3oldest_key Tff7b72= Tffa657minTb4b4b4(
Te6edf3candidatesTb4b4b4,
Te6edf3keyTff7b72=Tff7b72lambda Te6edf3kTb4b4b4: Te6edf3_rns_link_last_usedTff7b72.Td2a8ffgetTb4b4b4(Te6edf3kTb4b4b4, T79c0ff0.0Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3to_teardownTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rns_cached_linksTff7b72.Td2a8ffpopTb4b4b4(Te6edf3oldest_keyTb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4)
Te6edf3_rns_link_last_usedTff7b72.Td2a8ffpopTb4b4b4(Te6edf3oldest_keyTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3oldest_keyTb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72return Te6edf3to_teardown
Tff7b72def Td2a8ffsweep_stale_linksTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3now Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Te6edf3to_teardown Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Te6edf3stale Tff7b72= Tb4b4b4[
Te6edf3k Tff7b72for Te6edf3kTb4b4b4, Te6edf3v Tff7b72in Te6edf3rns_cached_linksTff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4) Tff7b72if Te6edf3vTff7b72.Td2a8ffstatus Tff7b72is Tff7b72not Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffACTIVE
Tb4b4b4]
Tff7b72for Te6edf3k Tff7b72in Te6edf3staleTb4b4b4:
Te6edf3to_teardownTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rns_cached_linksTff7b72.Td2a8ffpopTb4b4b4(Te6edf3kTb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4)
Te6edf3_rns_link_last_usedTff7b72.Td2a8ffpopTb4b4b4(Te6edf3kTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3kTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3idle Tff7b72= Tb4b4b4[
Te6edf3k
Tff7b72for Te6edf3kTb4b4b4, Te6edf3last Tff7b72in Te6edf3_rns_link_last_usedTff7b72.Td2a8ffitemsTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3k Tff7b72in Te6edf3rns_cached_links Tff7b72and Te6edf3now Tff7b72- Te6edf3last Tff7b72> Te6edf3LINK_IDLE_TTL_S
Tb4b4b4]
Tff7b72for Te6edf3k Tff7b72in Te6edf3idleTb4b4b4:
Te6edf3to_teardownTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rns_cached_linksTff7b72.Td2a8ffpopTb4b4b4(Te6edf3kTb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4)
Te6edf3_rns_link_last_usedTff7b72.Td2a8ffpopTb4b4b4(Te6edf3kTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3kTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3to_teardownTff7b72.Td2a8ffextendTb4b4b4(Te6edf3_evict_over_cap_lockedTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e# Drop counter entries whose link is no longer cached so the dict
T8b949e# cannot grow unbounded across link churn.
Te6edf3orphans Tff7b72= Tb4b4b4[Te6edf3k Tff7b72for Te6edf3k Tff7b72in Te6edf3_link_failure_counts Tff7b72if Te6edf3k Tff7b72not Tff7b72in Te6edf3rns_cached_linksTb4b4b4]
Tff7b72for Te6edf3k Tff7b72in Te6edf3orphansTb4b4b4:
Tff7b72del Te6edf3_link_failure_countsTb4b4b4[Te6edf3kTb4b4b4]
Te6edf3used_orphans Tff7b72= Tb4b4b4[Te6edf3k Tff7b72for Te6edf3k Tff7b72in Te6edf3_rns_link_last_used Tff7b72if Te6edf3k Tff7b72not Tff7b72in Te6edf3rns_cached_linksTb4b4b4]
Tff7b72for Te6edf3k Tff7b72in Te6edf3used_orphansTb4b4b4:
Tff7b72del Te6edf3_rns_link_last_usedTb4b4b4[Te6edf3kTb4b4b4]
Te6edf3_teardown_linksTb4b4b4(Te6edf3to_teardownTb4b4b4)
Tff7b72def Td2a8ffclear_all_cached_linksTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657intTb4b4b4:
T8b949e"""Tear down every cached RNS link (used after RNS hot reload).
sweep_stale_links leaves ACTIVE links alone. After Transport reset those
objects are tied to the old stack and must be dropped.
"""
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Te6edf3to_teardown Tff7b72= Tffa657listTb4b4b4(Te6edf3rns_cached_linksTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3rns_cached_linksTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Te6edf3_rns_link_last_usedTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Te6edf3_teardown_linksTb4b4b4(Te6edf3to_teardownTb4b4b4)
Tff7b72return Tffa657lenTb4b4b4(Te6edf3to_teardownTb4b4b4)
Tff7b72def Td2a8ff_cache_link_if_activeTb4b4b4(Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3linkTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3link Tff7b72is Tff7b72None Tff7b72or Te6edf3linkTff7b72.Td2a8ffstatus Tff7b72is Tff7b72not Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffACTIVETb4b4b4:
Tff7b72return
Te6edf3key Tff7b72= Tb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Te6edf3to_teardown Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Te6edf3rns_cached_linksTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Te6edf3link
Te6edf3_rns_link_last_usedTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
T8b949e# A freshly cached link starts with a clean failure count, even if
T8b949e# an older link at the same key died with a non-zero count.
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3to_teardownTff7b72.Td2a8ffextendTb4b4b4(Te6edf3_evict_over_cap_lockedTb4b4b4(Te6edf3preserve_keyTff7b72=Te6edf3keyTb4b4b4)Tb4b4b4)
Te6edf3_teardown_linksTb4b4b4(Te6edf3to_teardownTb4b4b4)
Tff7b72def Td2a8ff_uncache_link_if_matchesTb4b4b4(Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3linkTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return
Te6edf3key Tff7b72= Tb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Tff7b72if Te6edf3rns_cached_linksTff7b72.Td2a8ffgetTb4b4b4(Te6edf3keyTb4b4b4) Tff7b72is Te6edf3linkTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72del Te6edf3rns_cached_linksTb4b4b4[Te6edf3keyTb4b4b4]
Tff7b72except Tf85149KeyErrorTb4b4b4:
Tff7b72pass
Te6edf3_rns_link_last_usedTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72def Td2a8ff_reset_failure_countTb4b4b4(Te6edf3keyTb4b4b4: Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657bytesTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72def Td2a8ff_record_failure_and_maybe_recycleTb4b4b4(Te6edf3keyTb4b4b4: Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657bytesTb4b4b4]Tb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Tffa657intTb4b4b4, Tffa657boolTb4b4b4]Tb4b4b4:
T8b949e"""Increment the failure counter for key.
If the threshold is reached, pop the cached link, clear the counter,
and tear the link down outside the lock (teardown synchronously
re-enters via _on_link_closed → _uncache_link_if_matches).
Returns (new_count, recycled).
"""
Te6edf3link_to_teardown Tff7b72= Tff7b72None
Tff7b72with Te6edf3_rns_links_lockTb4b4b4:
Te6edf3n Tff7b72= Te6edf3_link_failure_countsTff7b72.Td2a8ffgetTb4b4b4(Te6edf3keyTb4b4b4, T79c0ff0Tb4b4b4) Tff7b72+ T79c0ff1
Tff7b72if Te6edf3n Tff7b72< Te6edf3_LINK_RECYCLE_FAILURE_THRESHOLDTb4b4b4:
Te6edf3_link_failure_countsTb4b4b4[Te6edf3keyTb4b4b4] Tff7b72= Te6edf3n
Tff7b72return Te6edf3nTb4b4b4, Tff7b72False
Te6edf3link_to_teardown Tff7b72= Te6edf3rns_cached_linksTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3_link_failure_countsTff7b72.Td2a8ffpopTb4b4b4(Te6edf3keyTb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72if Te6edf3link_to_teardown Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3link_to_teardownTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff[rns_link_manager] recycle teardown raised: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3nTb4b4b4, Tff7b72True
Tff7b72def Td2a8ff_split_aspectTb4b4b4(Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Tffa657strTb4b4b4, Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4]Tb4b4b4:
Te6edf3parts Tff7b72= Tb4b4b4[Te6edf3p Tff7b72for Te6edf3p Tff7b72in Te6edf3aspectTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4) Tff7b72if Te6edf3pTb4b4b4]
Tff7b72if Tff7b72not Te6edf3partsTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(Ta5d6ff"Ta5d6ffaspect must be a non-empty dot-separated stringTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3partsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4, Te6edf3partsTb4b4b4[T79c0ff1Tb4b4b4:Tb4b4b4]
Tff7b72class T56d364RnsLinkManagerTb4b4b4:
T8b949e"""Generic RNS Link lifecycle / request / packet helper.
The web layer wires three callables:
- self_identity_getter: returns the local RNS.Identity (or None).
- reticulum_getter: returns the MeshChat reticulum-like (used by
reticulum_pathfinding.prepare_fresh_path_request).
- broadcast_event: called with a JSON-serializable dict; expected to
forward to all interested /ws clients.
"""
Tff7b72def Tff7b72__init__Tb4b4b4(
Tff7b72selfTb4b4b4,
Tff7b72*Tb4b4b4,
Te6edf3self_identity_getterTb4b4b4: Te6edf3CallableTb4b4b4[Tb4b4b4[Tb4b4b4]Tb4b4b4, Te6edf3OptionalTb4b4b4[Ta5d6ff"Ta5d6ffRNS.IdentityTa5d6ff"Tb4b4b4]Tb4b4b4]Tb4b4b4,
Te6edf3reticulum_getterTb4b4b4: Te6edf3CallableTb4b4b4[Tb4b4b4[Tb4b4b4]Tb4b4b4, Te6edf3ReticulumLike Tff7b72| Tff7b72NoneTb4b4b4]Tb4b4b4,
Te6edf3broadcast_eventTb4b4b4: Te6edf3CallableTb4b4b4[Tb4b4b4[Tffa657dictTb4b4b4]Tb4b4b4, Tff7b72NoneTb4b4b4]Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_get_identity Tff7b72= Te6edf3self_identity_getter
Tff7b72selfTff7b72.Td2a8ff_get_reticulum Tff7b72= Te6edf3reticulum_getter
Tff7b72selfTff7b72.Td2a8ff_broadcast Tff7b72= Te6edf3broadcast_event
Tff7b72async Tff7b72def Td2a8ffopen_linkTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4,
Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4,
Tff7b72*Tb4b4b4,
Te6edf3auto_identifyTb4b4b4: Tffa657bool Tff7b72= Tff7b72FalseTb4b4b4,
Te6edf3on_phaseTb4b4b4: Te6edf3CallableTb4b4b4[Tb4b4b4[Tffa657strTb4b4b4]Tb4b4b4, Tff7b72NoneTb4b4b4] Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Te6edf3path_lookup_timeoutTb4b4b4: Tffa657float Tff7b72= T79c0ff15.0Tb4b4b4,
Te6edf3link_establishment_timeoutTb4b4b4: Tffa657float Tff7b72= T79c0ff15.0Tb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Te6edf3OptionalTb4b4b4[Ta5d6ff"Ta5d6ffRNS.LinkTa5d6ff"Tb4b4b4]Tb4b4b4, Tffa657boolTb4b4b4, Tffa657str Tff7b72| Tff7b72NoneTb4b4b4]Tb4b4b4:
T8b949e"""Open (or reuse) a Link to (aspect, destination_hash).
Returns (link, identified, failure_reason). On failure link is None
and failure_reason is set; otherwise failure_reason is None.
"""
Te6edf3app_nameTb4b4b4, Te6edf3sub_aspects Tff7b72= Te6edf3_split_aspectTb4b4b4(Te6edf3aspectTb4b4b4)
Tff7b72def Td2a8ff_phaseTb4b4b4(Te6edf3pTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3on_phase Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3on_phaseTb4b4b4(Te6edf3pTb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Te6edf3cached Tff7b72= Te6edf3get_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3cached Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3identified Tff7b72= Tff7b72False
Tff7b72if Te6edf3auto_identifyTb4b4b4:
Te6edf3identity Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_identityTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_local_identityTa5d6ff"
Te6edf3_phaseTb4b4b4(Ta5d6ff"Ta5d6ffidentifyingTa5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3cachedTff7b72.Td2a8ffidentifyTb4b4b4(Te6edf3identityTb4b4b4)
Te6edf3identified Tff7b72= Tff7b72True
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffidentify_failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tff7b72return Te6edf3cachedTb4b4b4, Te6edf3identifiedTb4b4b4, Tff7b72None
T8b949e# Path lookup
Te6edf3reticulum_pathfindingTff7b72.Td2a8ffprepare_fresh_path_requestTb4b4b4(
Tff7b72selfTff7b72.Td2a8ff_get_reticulumTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3destination_hashTb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3_phaseTb4b4b4(Ta5d6ff"Ta5d6fffinding_pathTa5d6ff"Tb4b4b4)
Te6edf3deadline Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3path_lookup_timeout
Tff7b72tryTb4b4b4:
Tff7b72while Tb4b4b4(
Tff7b72not Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72and Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72< Te6edf3deadline
Tb4b4b4)Tb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(Te6edf3_POLL_INTERVAL_STb4b4b4)
Tff7b72except Te6edf3asyncioTff7b72.Td2a8ffCancelledErrorTb4b4b4:
T8b949e# No link object yet, so there is nothing to tear down. Just propagate.
Tff7b72raise
Tff7b72if Tff7b72not Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_path_to_destinationTa5d6ff"
T8b949e# Re-check cache after path discovery (some other request may have
T8b949e# established a link in parallel).
Te6edf3cached Tff7b72= Te6edf3get_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3cached Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3identified Tff7b72= Tff7b72False
Tff7b72if Te6edf3auto_identifyTb4b4b4:
Te6edf3identity Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_identityTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_local_identityTa5d6ff"
Te6edf3_phaseTb4b4b4(Ta5d6ff"Ta5d6ffidentifyingTa5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3cachedTff7b72.Td2a8ffidentifyTb4b4b4(Te6edf3identityTb4b4b4)
Te6edf3identified Tff7b72= Tff7b72True
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffidentify_failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tff7b72return Te6edf3cachedTb4b4b4, Te6edf3identifiedTb4b4b4, Tff7b72None
Te6edf3_phaseTb4b4b4(Ta5d6ff"Ta5d6ffestablishing_linkTa5d6ff"Tb4b4b4)
Te6edf3identity Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8ffrecallTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_identity_for_destinationTa5d6ff"
Te6edf3destination Tff7b72= Te6edf3RNSTff7b72.Td2a8ffDestinationTb4b4b4(
Te6edf3identityTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffOUTTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffSINGLETb4b4b4,
Te6edf3app_nameTb4b4b4,
Tff7b72*Te6edf3sub_aspectsTb4b4b4,
Tb4b4b4)
Te6edf3link Tff7b72= Te6edf3RNSTff7b72.Td2a8ffLinkTb4b4b4(Te6edf3destinationTb4b4b4)
Te6edf3linkTff7b72.Td2a8ffset_packet_callbackTb4b4b4(
Tff7b72lambda Te6edf3dataTb4b4b4, Te6edf3packetTb4b4b4, Te6edf3_aspectTff7b72=Te6edf3aspectTb4b4b4, Te6edf3_dhTff7b72=Te6edf3destination_hashTb4b4b4: Tff7b72selfTff7b72.Td2a8ff_on_packetTb4b4b4(
Te6edf3_aspectTb4b4b4,
Te6edf3_dhTb4b4b4,
Te6edf3dataTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3linkTff7b72.Td2a8ffset_link_closed_callbackTb4b4b4(
Tff7b72lambda Te6edf3lnkTb4b4b4, Te6edf3_aspectTff7b72=Te6edf3aspectTb4b4b4, Te6edf3_dhTff7b72=Te6edf3destination_hashTb4b4b4: Tff7b72selfTff7b72.Td2a8ff_on_link_closedTb4b4b4(
Te6edf3_aspectTb4b4b4,
Te6edf3_dhTb4b4b4,
Te6edf3lnkTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3deadline Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3link_establishment_timeout
Tff7b72tryTb4b4b4:
Tff7b72while Te6edf3linkTff7b72.Td2a8ffstatus Tff7b72is Tff7b72not Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffACTIVE Tff7b72and Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72< Te6edf3deadlineTb4b4b4:
Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffsleepTb4b4b4(Te6edf3_POLL_INTERVAL_STb4b4b4)
Tff7b72except Te6edf3asyncioTff7b72.Td2a8ffCancelledErrorTb4b4b4:
T8b949e# Caller bailed (typically: WS client disconnected). Tear down the
T8b949e# half-built link so it doesn't sit half-established consuming
T8b949e# RNS bookkeeping for the destination.
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72raise
Tff7b72if Te6edf3linkTff7b72.Td2a8ffstatus Tff7b72is Tff7b72not Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffACTIVETb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6fflink_establishment_timeoutTa5d6ff"
Te6edf3_cache_link_if_activeTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3linkTb4b4b4)
Te6edf3identified Tff7b72= Tff7b72False
Tff7b72if Te6edf3auto_identifyTb4b4b4:
Te6edf3identity_local Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_identityTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3identity_local Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_local_identityTa5d6ff"
Te6edf3_phaseTb4b4b4(Ta5d6ff"Ta5d6ffidentifyingTa5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffidentifyTb4b4b4(Te6edf3identity_localTb4b4b4)
Te6edf3identified Tff7b72= Tff7b72True
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tff7b72FalseTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffidentify_failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tff7b72return Te6edf3linkTb4b4b4, Te6edf3identifiedTb4b4b4, Tff7b72None
Tff7b72def Td2a8ffidentifyTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4,
Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Tffa657boolTb4b4b4, Tffa657str Tff7b72| Tff7b72NoneTb4b4b4]Tb4b4b4:
Te6edf3link Tff7b72= Te6edf3get_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_active_linkTa5d6ff"
Te6edf3identity Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_identityTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3identity Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_local_identityTa5d6ff"
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffidentifyTb4b4b4(Te6edf3identityTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffidentify_failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tff7b72return Tff7b72TrueTb4b4b4, Tff7b72None
Tff7b72def Td2a8ffrequestTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4,
Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4,
Te6edf3pathTb4b4b4: Tffa657strTb4b4b4,
Te6edf3dataTb4b4b4,
Te6edf3response_callbackTb4b4b4,
Te6edf3failed_callbackTb4b4b4,
Te6edf3progress_callbackTb4b4b4,
Te6edf3timeoutTb4b4b4: Tffa657float Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3link Tff7b72= Te6edf3get_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72raise Tf85149RuntimeErrorTb4b4b4(Ta5d6ff"Ta5d6ffno_active_linkTa5d6ff"Tb4b4b4)
Te6edf3key Tff7b72= Tb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72def Td2a8ff_wrapped_responseTb4b4b4(Te6edf3receiptTb4b4b4, Te6edf3_cbTff7b72=Te6edf3response_callbackTb4b4b4, Te6edf3_keyTff7b72=Te6edf3keyTb4b4b4)Tb4b4b4:
Te6edf3_reset_failure_countTb4b4b4(Te6edf3_keyTb4b4b4)
Te6edf3_cbTb4b4b4(Te6edf3receiptTb4b4b4)
Tff7b72def Td2a8ff_wrapped_failedTb4b4b4(Te6edf3receiptTff7b72=Tff7b72NoneTb4b4b4, Te6edf3_cbTff7b72=Te6edf3failed_callbackTb4b4b4, Te6edf3_keyTff7b72=Te6edf3keyTb4b4b4)Tb4b4b4:
Te6edf3_countTb4b4b4, Te6edf3recycled Tff7b72= Te6edf3_record_failure_and_maybe_recycleTb4b4b4(Te6edf3_keyTb4b4b4)
Tff7b72if Te6edf3recycledTb4b4b4:
T8b949e# The cached link has been torn down and evicted, so the next
T8b949e# rns.link.request to this destination will re-establish.
T8b949e# The existing link_closed event already fires from
T8b949e# _on_link_closed via link.teardown(), so clients that watch
T8b949e# for it can react.
T8b949e#
T8b949e# Future enhancement (option B from design): broadcast a
T8b949e# dedicated rns.link.event with
T8b949e# event="link_recycled_after_failures", failures=_count,
T8b949e# destination_hash=_key[1].hex(), aspect=_key[0], which is useful
T8b949e# for UIs that want to surface "link reset after N failures"
T8b949e# diagnostics distinct from a plain link_closed.
Tff7b72pass
Te6edf3_cbTb4b4b4(Te6edf3receiptTb4b4b4)
Tff7b72return Te6edf3linkTff7b72.Td2a8ffrequestTb4b4b4(
Te6edf3pathTb4b4b4,
Te6edf3dataTff7b72=Te6edf3dataTb4b4b4,
Te6edf3response_callbackTff7b72=Te6edf3_wrapped_responseTb4b4b4,
Te6edf3failed_callbackTff7b72=Te6edf3_wrapped_failedTb4b4b4,
Te6edf3progress_callbackTff7b72=Te6edf3progress_callbackTb4b4b4,
Te6edf3timeoutTff7b72=Te6edf3timeoutTb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffsend_packetTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4,
Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4,
Te6edf3payloadTb4b4b4: Tffa657bytesTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Tffa657boolTb4b4b4, Tffa657str Tff7b72| Tff7b72NoneTb4b4b4]Tb4b4b4:
Te6edf3link Tff7b72= Te6edf3get_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Ta5d6ff"Ta5d6ffno_active_linkTa5d6ff"
Tff7b72tryTb4b4b4:
Te6edf3RNSTff7b72.Td2a8ffPacketTb4b4b4(Te6edf3linkTb4b4b4, Te6edf3payloadTb4b4b4)Tff7b72.Td2a8ffsendTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffsend_failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tff7b72return Tff7b72TrueTb4b4b4, Tff7b72None
Tff7b72def Td2a8ffcloseTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
Te6edf3link Tff7b72= Te6edf3get_cached_active_linkTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3link Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3_uncache_link_if_matchesTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3linkTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff[rns_link_manager] close teardown raised: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ff_on_packetTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3dataTb4b4b4: Tffa657bytesTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_broadcastTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrns.link.eventTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffeventTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffpacket_receivedTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4: Te6edf3destination_hashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4: Te6edf3aspectTb4b4b4,
Ta5d6ff"Ta5d6ffpayload_b64Ta5d6ff"Tb4b4b4: Te6edf3base64Tff7b72.Td2a8ffb64encodeTb4b4b4(Tffa657bytesTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4)Tff7b72.Td2a8ffdecodeTb4b4b4(Ta5d6ff"Ta5d6ffasciiTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72def Td2a8ff_on_link_closedTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3aspectTb4b4b4: Tffa657strTb4b4b4, Te6edf3destination_hashTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3linkTb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Te6edf3_uncache_link_if_matchesTb4b4b4(Te6edf3aspectTb4b4b4, Te6edf3destination_hashTb4b4b4, Te6edf3linkTb4b4b4)
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_broadcastTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrns.link.eventTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffeventTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6fflink_closedTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4: Te6edf3destination_hashTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4: Te6edf3aspectTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────